package com.dtflys.easyel;

import com.dtflys.easyel.compile.EasyElCompileConfiguration;
import com.dtflys.easyel.compile.EasyElCompiledResult;
import com.dtflys.easyel.compile.EasyElCompiler;
import com.dtflys.easyel.compile.EasyElSource;
import com.dtflys.easyel.runtime.EasyElEvalVisitor;
import com.dtflys.easyel.runtime.EasyElRuntimeContext;
import com.dtflys.easyel.utils.MetaHelper;
import com.dtflys.easyel.utils.ThrowUtils;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * @author gongjun[jun.gong@thebeastshop.com]
 * @since v1.0.0
 */
public class EasyEl {

    public static Boolean evalAsBoolean(String sourceText, EasyElContext context) {
        return eval(sourceText, context, Boolean.class);
    }

    public static String evalAsString(String sourceText, EasyElContext context) {
        return String.valueOf(eval(sourceText, context));
    }

    public static Short evalAsShort(String sourceText, EasyElContext context) {
        return eval(sourceText, context, Short.class);
    }

    public static Integer evalAsInteger(String sourceText, EasyElContext context) {
        return eval(sourceText, context, Integer.class);
    }

    public static Long evalAsLong(String sourceText, EasyElContext context) {
        return eval(sourceText, context, Long.class);
    }

    public static BigInteger evalAsBigInteger(String sourceText, EasyElContext context) {
        return eval(sourceText, context, BigInteger.class);
    }

    public static Float evalAsFloat(String sourceText, EasyElContext context) {
        return eval(sourceText, context, Float.class);
    }

    public static Double evalAsDouble(String sourceText, EasyElContext context) {
        return eval(sourceText, context, Double.class);
    }

    public static BigDecimal evalAsBigDecimal(String sourceText, EasyElContext context) {
        return eval(sourceText, context, BigDecimal.class);
    }

    public static Map<?, ?> evalAsMap(String sourceText, EasyElContext context) {
        return eval(sourceText, context, Map.class);
    }

    public static Object eval(String sourceText, EasyElContext context) {
        EasyElCompiledResult compiledResult = compile(sourceText, context.getCompileConfiguration());
        return eval(compiledResult, context);
    }

    public static <T> T eval(String sourceText, EasyElContext context, Class<T> retuenType) {
        EasyElCompiledResult compiledResult = compile(sourceText, context.getCompileConfiguration());
        return eval(compiledResult, context, retuenType);
    }

    public static Object eval(EasyElCompiledResult compiledResult, EasyElContext context) {
        return eval(compiledResult, context, Object.class);
    }

    public static <T> T eval(EasyElCompiledResult compiledResult, EasyElContext context, Class<T> retuenType) {
        EasyElEvalVisitor visitor = new EasyElEvalVisitor();
        EasyElRuntimeContext runtimeContext = cloneContext(context, compiledResult);
        try {
            compiledResult.getAstNode().visit(runtimeContext, visitor);
        } catch (RuntimeException ex) {
            ThrowUtils.throwWithoutEasyElPackages(ex);
        }
        Object result = runtimeContext.getResult();
        if (result instanceof List) {
            result = MetaHelper.unwrap((List<?>) result);
        } else {
            result = MetaHelper.unwrap(result);
        }
        return MetaHelper.cast(retuenType, result);
    }

    public static EasyElCompiledResult compile(String sourceText) {
        return compile(sourceText, new EasyElCompileConfiguration());
    }

    public static EasyElCompiledResult compile(String sourceText, EasyElCompileConfiguration compileConfiguration) {
        EasyElSource source = new EasyElSource(sourceText, compileConfiguration.getSourceName());
        EasyElCompiler compiler = new EasyElCompiler(source, compileConfiguration);
        return compiler.compile();
    }


    private static EasyElRuntimeContext cloneContext(EasyElContext context, EasyElCompiledResult compiledResult) {
        EasyElRuntimeContext runtimeContext = new EasyElRuntimeContext();
        Map<String, Object> newEnv = new HashMap<>();
        Map<String, Object> env = context.getEnv();
        for (Iterator<Map.Entry<String, Object>> iterator = env.entrySet().iterator(); iterator.hasNext(); ) {
            Map.Entry<String, Object> entry = iterator.next();
            newEnv.put(entry.getKey(), entry.getValue());
        }
        runtimeContext.setSource(compiledResult.getSource());
        runtimeContext.setEnv(newEnv);
        return runtimeContext;
    }

}
